<?php
declare(strict_types=1);

namespace App\Helper;

use App\Exception\BusinessException;
use Hyperf\View\Engine\EngineInterface;

class TplHelper implements EngineInterface
{
    private static $strTplPath = null; // 模板目标
    private static $strCompiledPath = null; // 编译后的文件目录
    private static $strLeft = '{{'; // 变量左符号
    private static $strRight = '}}'; // 变量右符号

    /**
     * 编码回调函数，处理文件包含
     * @param array $arrMatch
     * @return string
     */
    private static function callback4include(array $arrMatch): string
    {
        if (!is_array($arrMatch) || count($arrMatch) != 2) {
            return '';
        }
        $strFile = trim($arrMatch[1]);
        return '<?php include(\\App\\Helper\\TplHelper::getCompiledFile(\'' . $strFile . '\') ?? \'' . $strFile . '\'); ?>';
    }

    /**
     * 继承
     * @param $template
     * @param $data
     * @param $config
     * @return string
     */
    public function render($template, $data, $config): string
    {
        $viewPath = $config['view_path'] ?? '';
        $cachePath = $config['cache_path'] ?? '';
        if (empty($viewPath) || empty($cachePath)) {
            throw new BusinessException(0, '视图配置错误！');
        }
        is_null(static::$strTplPath) && static::$strTplPath = $viewPath;
        is_null(static::$strCompiledPath) && static::$strCompiledPath = $cachePath;
        return static::fetchHtml($template, $data);
    }

    /**
     * 取模板输出的HTML
     * @param string $strTpl
     * @param array $arrAssign
     * @return string
     */
    public static function fetchHtml(string $strTpl, array $arrAssign = []): string
    {
        $strCompiledFile = static::getCompiledFile($strTpl);
        if (!$strCompiledFile) {
            return '';
        }
        extract($arrAssign);
        unset($arrAssign);
        ob_start();
        include($strCompiledFile);
        return ob_get_clean();
    }

    /**
     * 取编译后的模板文件
     * @param string $strTpl
     * @return string|null
     */
    private static function getCompiledFile(string $strTpl): ?string
    {
        $re = null;
        $strTpl = ltrim($strTpl, '/');
        $strTplFile = static::$strTplPath . '/' . $strTpl;
        $strCompiledFile = static::$strCompiledPath . '/' . $strTpl;
        if (is_file($strTplFile)) {
            if (is_file($strCompiledFile) && filemtime($strCompiledFile) >= filemtime($strTplFile)) {
                return $strCompiledFile;
            }
            if (FileHelper::write($strCompiledFile, static::getCompiledCode($strTplFile))) {
                return $strCompiledFile;
            }
            return null;
        }
        FileHelper::del($strCompiledFile);
        return null;
    }

    /**
     * 取编译后的模板字符串
     * @param string $strTplFile
     * @return string|null
     */
    private static function getCompiledCode(string $strTplFile): ?string
    {
        $str = FileHelper::read($strTplFile);
        if (!$str) {
            return null;
        }
        $left = static::$strLeft;
        $right = static::$strRight;
        /* include */
        $str = preg_replace_callback('/' . $left . 'include\s+[\'|\"]([^{]+?)[\'|\"]' . $right . '/s', array(__CLASS__, 'callback4include'), $str);
        /* if操作 */
        $str = preg_replace('/' . $left . 'if\s+([^{]+?)' . $right . '/s', '<?php if (\\1) { ?>', $str);
        $str = preg_replace('/' . $left . 'else' . $right . '/s', '<?php } else { ?>', $str);
        $str = preg_replace('/' . $left . 'elseif\s+([^{]+?)' . $right . '/s', '<?php } elseif (\\1) { ?>', $str);
        $str = preg_replace('/' . $left . '\/if' . $right . '/s', '<?php } ?>', $str);
        /* foreach操作 */
        $str = preg_replace('/' . $left . 'foreach\s+([^{]+?)' . $right . '/s', '<?php foreach (\\1) { ?>', $str);
        $str = preg_replace('/' . $left . '\/foreach' . $right . '/s', '<?php } ?>', $str);
        /* for操作 */
        $str = preg_replace('/' . $left . 'for\s+([^{]+?)' . $right . '/s', '<?php for (\\1) { ?>', $str);
        $str = preg_replace('/' . $left . '\/for' . $right . '/s', '<?php } ?>', $str);
        /* 输出变量 */
        $str = preg_replace('/' . $left . '(\\$[a-zA-Z_\x7f-\xff][a-zA-Z0-9_$\x7f-\xff\->\[\]\']*)' . $right . '/s', '<?php echo \\1; ?>', $str);
        /* 常量输出 */
        $str = preg_replace('/' . $left . '([A-Z_\x7f-\xff][A-Z0-9_\x7f-\xff]*)' . $right . '/s', '<?php echo \\1; ?>', $str);
        /* PHP标签 */
        $str = preg_replace('/' . $left . '\s*([^{]+?)\s*' . $right . '/s', '<?php \\1 ?>', $str);
        return preg_replace('/\s\?>\n*\s*<\?php\s/', ' ', $str);
    }
}
